#pragma once
#ifdef __ANDROID__

#include <jni.h>
#include <csignal>
#include "../../App.h"

#include <cstdlib>
#include <unistd.h>
#include <execinfo.h>// call stack info
#include <unwind.h>// stack unwinding
#include <dlfcn.h>// dynamic link lib operation interface
#include <cxxabi.h>// demangle C++ ABI

// Shortcuts for JNI functions.
#define Viewer3D_JNI(FUNC_NAME, ...) \
	extern "C" JNIEXPORT void JNICALL \
	Java_com_hb_sq3d_Viewer3D_ ## FUNC_NAME( \
		JNIEnv *env, jclass /* jclz */, ##__VA_ARGS__ \
	)

#define Viewer3D_JNI_RET(FUNC_NAME, ...) \
	JNICALL Java_com_hb_sq3d_Viewer3D_ ## FUNC_NAME( \
		JNIEnv *env, jclass /* jclz */, ##__VA_ARGS__ \
	)

#define RET_INT extern "C" JNIEXPORT jint

class AApp {
public:
	static void init() {
		App::init();
	}

	static void setJavaVM(JavaVM* javaVm) { s_javaVM = javaVm; }

	/**@note Any non Java started thread must first be attached to the JVM before making JNI calls. */
	static bool attachCurrentThread() {
		JNIEnv* env;
		JavaVMAttachArgs args;
		args.version = JNI_VERSION_1_6;// specify jni version
		args.name = nullptr;// name the thread
		args.group = nullptr;// use the default thread group
		// attach current thread to JVM
		auto stat = s_javaVM->AttachCurrentThread(&env, &args);
		return stat == JNI_OK;
	}

	// after call it, cannot call any JNI functions any more!
	static void detachCurrentThread() { s_javaVM->DetachCurrentThread(); }

	/**@example callJavaStaticVoidMethod(env, clazz, "handleStatus", 123); => JAVA::handleStatus(123); */
	template<typename T>
	static void callJavaStaticVoidMethod(JNIEnv *env, jclass clazz, const char* methodName, T argValue) {
		const char* sig = "";
		if constexpr (std::is_same_v<T, int>) sig = "(I)V";
		else if constexpr (std::is_same_v<T, float>) sig = "(F)V";
		else if constexpr (std::is_same_v<T, bool>) sig = "(Z)V";
		else static_assert(false, "Unsupported type: only jint/jfloat/jboolean are allowed!");

		jmethodID method = env->GetStaticMethodID(clazz, methodName, sig);
		if (method)
			env->CallStaticVoidMethod(clazz, method, argValue);
		else
			env->ExceptionClear();// clear the Java exception state of the current thread
	}

	static void callJavaStaticVoidMethod(JNIEnv *env, jclass clazz, const char* methodName) {
		jmethodID method = env->GetStaticMethodID(clazz, methodName, "()V");
		if (method)
			env->CallStaticVoidMethod(clazz, method);
		else
			env->ExceptionClear();// clear the Java exception state of the current thread
	}
	static void signalHandler(int signal) {
		std::signal(signal, SIG_IGN);// ignore, ensure be called only once!
		std::string msg;
		switch(signal) {
			case SIGSEGV:
				msg = "Segmentation fault (SIGSEGV) occurred";
				break;
			case SIGABRT:
				msg = "Abort signal (SIGABRT) occurred";
				break;
			default:
				msg = std::string("Unknown signal occurred! (") + std::to_string(signal) + ")";
		}
		print_backtrace();
		throw std::runtime_error(msg);// Debug-Mode: can be caught by try-catch; Release-Mode: can not be caught, will crash!
	}

	static void registerSignalHandler() {
		std::signal(SIGSEGV, signalHandler);// set again, because of setting to SIG_IGN when signal handled
	}

private:
	static JavaVM* s_javaVM;

	struct BacktraceState {
		void** current;
		void** end;
	};

	static void print_backtrace() {
		const size_t max = 100;
		void* buffer[max];
		size_t count = capture_backtrace(buffer, max);
		for (size_t i = 0; i < count; ++i) {
			const void* addr = buffer[i];
			Dl_info info;
			if (dladdr(addr, &info) && info.dli_sname) {
				int status = 0;
				char* demangled = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status);
				const char* name = (status == 0) ? demangled : info.dli_sname;
				__android_log_print(ANDROID_LOG_ERROR, "Crash", "#%zu: %p %s\n", i, addr, name);
				if (demangled) free(demangled);
			} else {
				__android_log_print(ANDROID_LOG_ERROR, "Crash", "#%zu: %p\n", i, addr);
			}
		}
	}

	static size_t capture_backtrace(void** buffer, size_t max) {
		BacktraceState state = {buffer, buffer + max};
		_Unwind_Backtrace(unwind_callback, &state);
		return state.current - buffer;
	}

	static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context* context, void* arg) {
		auto state = static_cast<BacktraceState*>(arg);
		uintptr_t pc = _Unwind_GetIP(context);
		if (pc) {
			if (state->current == state->end) {
				return _URC_END_OF_STACK;
			} else {
				*state->current++ = reinterpret_cast<void*>(pc);
			}
		}
		return _URC_NO_REASON;
	}
};

#endif